<?php
namespace WDB;

/**
 * BaseObject extension with collection behavior
 * 
 * Iterator, ArrayAccess and Countable implementation on internal array
 * or object implementing any of these interfaces.
 *
 * @author Richard Ejem <richard(at)ejem.cz>
 * @package WDB
 */
abstract class BaseCollection extends BaseObject implements \Iterator, \ArrayAccess, \Countable
{
    const WRITE_ALL = 1;
    const WRITE_NOT_UNSET = 2;
    const WRITE_EXISTING = 3;
    const WRITE_FORBIDDEN = 4;
    
    /** @var array|object $contents */
    protected $contents = array();
    protected $writeMode = self::WRITE_ALL;

    /**
     * @param string interface name
     * @throws Exception\InvalidOperation
     */
    private function requireInterface($name)
    {
        if (!is_array($this->contents) && !$this->contents instanceof $name) throw new Exception\InvalidOperation("Provided collection does not implement $name interface");
    }
    
    // <editor-fold defaultstate="collapsed" desc="\Countable interface implementation">
    public function count()
    {
        $this->requireInterface('\\Countable');
        return count($this->contents);
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="\Iterator interface implementation">
    public function current()
    {
        return $this->outValue($this->_current(), $this->key());
    }
    private function _current()
    {
        $this->requireInterface('\\Iterator');
        if (is_array($this->contents))
        {
            return current($this->contents);
        }
        else
        {
            return $this->contents->current();
        }
        
    }

    public function key()
    {
        $this->requireInterface('\\Iterator');
        if (is_array($this->contents))
        {
            return key($this->contents);
        }
        else
        {
            return $this->contents->key();
        }
    }

    public function next()
    {
        $val = $this->_next();
        return $this->outValue($val, $this->key());
    }
    private function _next()
    {
        $this->requireInterface('\\Iterator');
        if (is_array($this->contents))
        {
            return next($this->contents);
        }
        else
        {
            return $this->contents->next();
        }
        
    }

    public function rewind()
    {
        $this->requireInterface('\\Iterator');
        if (is_array($this->contents))
        {
            return reset($this->contents);
        }
        else
        {
            return $this->contents->rewind();
        }
        
    }

    public function valid()
    {
        $this->requireInterface('\\Iterator');
        if (is_array($this->contents))
        {
            $key = key($this->contents);
            return ($key !== NULL && $key !== FALSE);
        }
        else
        {
            return $this->contents->valid();
        }
    }
    // </editor-fold>
    
    // \ArrayAccess interface implementation -------------------------------------------------------------------------------------
    public function offsetExists($offset)
    {
        return isset($this->contents[$offset]);
    }

    public function offsetGet($offset)
    {
        return $this->outValue($this->contents[$offset], $offset);
    }

    public function offsetSet($offset, $value)
    {
        if ($this->writeMode == self::WRITE_FORBIDDEN ||
            $this->writeMode == self::WRITE_EXISTING && !isset($this->contents[$offset]))
        {
            throw new Exception\InvalidOperation("Cannot write key $offset to the collection");
        }
        if ($offset === NULL)
        {
            $this->contents[] = $this->processValue($value, $offset);
        }
        else
        {
            $this->contents[$offset] = $this->processValue($value, $offset);
        }
    }

    public function offsetUnset($offset)
    {
        if ($this->writeMode == self::WRITE_FORBIDDEN)
        {
            throw new Exception\InvalidOperation("Cannot unset properties from the collection");
        }
        if ($this->writeMode == self::WRITE_ALL)
        {
            unset($this->contents[$offset]);
        }
        else
        {
            $this->contents[$offset] = NULL;
        }
        
    }
    
    // END of PHP interfaces implementation --------------------------------------------------------------------------------------
    
    /**
     * Get this object's php array.
     *
     * @return array
     */
    public function toArray()
    {
        return $this->contents;
    }
    
    /**
     * Processes value before inserting into collection. Dedicated for overriding in child classes
     *
     * @param mixed $value
     * @param mixed $offset
     * @return mixed
     */
    protected function processValue($value, $offset)
    {
        return $value;
    }
    
    /**
     * Processes value before returning. Dedicated for overriding in child classes
     *
     * @param mixed $value
     * @param mixed $offset
     * @return mixed
     */
    protected function outValue($value, $offset)
    {
        return $value;
    }

}
